package render;

import geom.Geometry;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;

/**
 * saves all important information for .off Model
 * @author chris
 *
 */
public class ObjectModel {
	/** 
	 * vertices stored: 
	 * v[i+0] = x_i 
	 * v[i+1] = y_i
	 * v[i+2] = z_i 
	 */
	int numVertices, numEdges, numFaces;
	double[] vertices;
	double[] normals;
	double[] textCoords;
	double[] faceNormals;
	Triangle[] triangles;
	Material material;
	public static String OFF_IDENTIFIER = "OFF";
	
	/**
	 * Creats a Model from an OFF File <B>support only triangles yet</B>
	 * @param filenname
	 * @return
	 */
	public void createModel(String filename, double avSize) {
		// used for parse the file
		String line;
		String[] l;
		int ind1, ind2, ind3, numNodes;
		double[] fn_tmp, n_tmp;
		int[] ncounter;
		
		System.out.println("creating model ...");
		System.out.println("\t >> read model: "+filename);
		try {
			BufferedReader br = new BufferedReader(new FileReader(filename));
			line = br.readLine();
			
			if(!line.equals(OFF_IDENTIFIER)) {
				System.err.println("File is not a off File");
				return;
			}
			
			line = br.readLine();
			l = line.split(" ");
			numVertices = Integer.parseInt(l[0]);
			numFaces = Integer.parseInt(l[1]);
			numEdges = Integer.parseInt(l[2]);
			System.out.println("\t >> numVertices: "+numVertices+" numFaces: "+numFaces+" numEdges: "+numEdges);
			// get vertices <- finished
			vertices = getVertices(numVertices, br);
			normals = new double[numVertices*3];
			textCoords = new double[numVertices*3];
			faceNormals = new double[numFaces*3];
			triangles = new Triangle[numFaces];
			ncounter = new int[numVertices];

			for(int i=0; i<numFaces; i++) {
				// parse vertex indices
				line = br.readLine();
				l = line.split(" ");
				numNodes = Integer.parseInt(l[0]);
				ind1 = Integer.parseInt(l[1]);
				ind2 = Integer.parseInt(l[2]);
				ind3 = Integer.parseInt(l[3]);
				if(numNodes != 3) {
					System.err.println("more than 3 nodes per face are not supported yet ...");
					System.exit(1);
				}
//				System.out.println("ind1: "+ind1+" ind2: "+ind2+" ind3: "+ind3);
				// create triangle <- finished
				triangles[i] = new Triangle(new int[]{ind1, ind2, ind3}, new int[]{ind1, ind2, ind3}, new int[3], this, i);
				
				// calculate facetnormal <- finished
				double[] v1 = Arrays.copyOfRange(vertices, ind1*3, ind1*3+3);
				double[] v2 = Arrays.copyOfRange(vertices, ind2*3, ind2*3+3);
				double[] v3 = Arrays.copyOfRange(vertices, ind3*3, ind3*3+3);
				fn_tmp = Geometry.computeNormal(v2, v1, v3);
				faceNormals[i*3+0] = fn_tmp[0];
				faceNormals[i*3+1] = fn_tmp[1];
				faceNormals[i*3+2] = fn_tmp[2];
				
				// facetnormal vote for vertex normal <- build mean at end
				// normal for vertex 1 
				n_tmp = Arrays.copyOfRange(normals, ind1*3, ind1*3+3);
				n_tmp = Geometry.sum(n_tmp, fn_tmp);
				normals[ind1*3+0] = n_tmp[0];
				normals[ind1*3+1] = n_tmp[1];
				normals[ind1*3+2] = n_tmp[2];
				ncounter[ind1]++;
				// normal for vertex 2 
				n_tmp = Arrays.copyOfRange(normals, ind2*3, ind2*3+3);
				n_tmp = Geometry.sum(n_tmp, fn_tmp);
				normals[ind2*3+0] = n_tmp[0];
				normals[ind2*3+1] = n_tmp[1];
				normals[ind2*3+2] = n_tmp[2];
				ncounter[ind2]++;
				// normal for vertex 3 
				n_tmp = Arrays.copyOfRange(normals, ind3*3, ind3*3+3);
				n_tmp = Geometry.sum(n_tmp, fn_tmp);
				normals[ind3*3+0] = n_tmp[0];
				normals[ind3*3+1] = n_tmp[1];
				normals[ind3*3+2] = n_tmp[2];
				ncounter[ind3]++;
			}
			
			double length = 0;
			for(int i=0; i<numVertices; i++) {
				// build mean of facetnormals <- finished computation of vertex normals
				n_tmp = Arrays.copyOfRange(normals, i*3, i*3+3);
				Geometry.multiply(n_tmp, 1.0/ncounter[i]);
				Geometry.normalize(n_tmp);
				normals[i*3+0] = n_tmp[0];
				normals[i*3+1] = n_tmp[1];
				normals[i*3+2] = n_tmp[2];
				
				length += Geometry.norm(Arrays.copyOfRange(vertices, i*3, i*3+3));
			}
			length /= numVertices;
			System.out.println("modelsize: "+length);
			double scale = avSize / length;
			length = 0;
			for(int i=0; i<numVertices; i++) {
				// scale model
				vertices[i*3+0] *= scale;
				vertices[i*3+1] *= scale;
				vertices[i*3+2] *= scale;
				length += Geometry.norm(Arrays.copyOfRange(vertices, i*3, i*3+3)); 
			}
			length /= numVertices;
			System.out.println("scaled modelsize: "+length);
		} catch (FileNotFoundException e) {
			System.err.println("File was not found "+e.getMessage());
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Can't read file");
			System.exit(1);
		}
	}
	
	private static double[] getVertices(int numVertices, BufferedReader br) throws IOException {
		double[] v = new double[numVertices*3];
		String line;
		String[] l;
		
		for(int i=0; i<numVertices; i++){
			line = br.readLine();
			l = line.split(" ");
			v[i*3+0] = Double.parseDouble(l[0]);
			v[i*3+1] = -Double.parseDouble(l[1]);
			v[i*3+2] = Double.parseDouble(l[2]);
		}
		
		return v;
	}
	
	public ObjectModel(String filename, Material material, double avSize) {
		this.material = material;
		createModel(filename, avSize);
	}
	
	public double[] getNormals() {
		return normals;
	}

	public void setNormals(double[] normals) {
		this.normals = normals;
	}

	public double[] getTextCoords() {
		return textCoords;
	}

	public void setTextCoords(double[] textCoords) {
		this.textCoords = textCoords;
	}

	public double[] getFaceNormals() {
		return faceNormals;
	}

	public void setFaceNormals(double[] faceNormals) {
		this.faceNormals = faceNormals;
	}

	public Triangle[] getTriangles() {
		return triangles;
	}

	public void setTriangles(Triangle[] triangles) {
		this.triangles = triangles;
	}

	public Material getMaterial() {
		return material;
	}

	public void setMaterial(Material material) {
		this.material = material;
	}

	public double[] getVertex(int vIndex) {
		double[] vertex = null; 
		    
		if(vIndex < vertices.length - 3) {
			vertex = new double[]{vertices[vIndex+0], vertices[vIndex+1], vertices[vIndex+2]};
		}
		
		return vertex;
	}
	
	public boolean setVertex (int vIndex, double[] v) {
		boolean ok = false;
		if(vIndex < v.length - 3) {
			this.vertices[vIndex+0] = v[0];
			this.vertices[vIndex+1] = v[1];
			this.vertices[vIndex+2] = v[2];
			ok = true;
		}
		
		return ok;
	}

	public double[] getVertices() {
		return vertices;
	}

	public void setVertices(double[] v) {
		this.vertices = v;
	}
	
}